﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace SilverlightChartMove
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

         #region Fields 
        public int AnimationDuration = 2;
        public List<Ellipse> ChartList = new List<Ellipse>();
        public List<Thickness> MarginList = new List<Thickness>() { 
            new Thickness(){ Left=123,Top=148 }, //left 123;148
            new Thickness(){ Left=397,Top=319}, //front 397；319
            new Thickness(){ Left=708,Top=64 }  //right 708；64
        };
        public double OriginWidth = 700;
        public double OriginHeight = 480;
        bool recur = false;

        #endregion
        
        #region Ellipse Events

        private void rightChart_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            var index =FindIndexByName(rightChart.Name);

            recur = false;
            if (index == 0)
            {
                recur = true;
                Animate();
            }
            else if (index == 2)
            {
                Animate(); 
            } 
        }

        private void frontChart_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            var index =FindIndexByName(frontChart.Name);

            recur = false;
            if (index == 0)
            {
                recur = true;
                Animate();
            }
            else if (index == 2)
            {
                Animate();
            } 
        }

        private void leftChart_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            var index = FindIndexByName(leftChart.Name);

            recur = false;
            if (index == 0)
            {
                recur = true;
                Animate();
            }
            else if (index == 2)
            {
                Animate();
            } 
        }

        #endregion

        #region Assist Methods

        void DrawLine()
        {
            Point leftChartCenter = GetEllipseCenter(leftChart);
            Point frontChartCenter = GetEllipseCenter(frontChart);
            Point rightChartCenter = GetEllipseCenter(rightChart); 
        }

        void Animate()
        {
            RemoveChartsEvents();
            var duration = TimeSpan.FromSeconds(AnimationDuration);
            Storyboard storyBoard = new Storyboard();
            storyBoard.Completed += new EventHandler(storyBoard_Completed);

            // right to front          
            storyBoard.Children.Add(ThicknessAnimation.Create(rightChart, Ellipse.MarginProperty, duration, rightChart.Margin, frontChart.Margin));

            DoubleAnimation rightToFrontWidthDoubleAnimation = new DoubleAnimation();
            rightToFrontWidthDoubleAnimation.Duration = duration;
            rightToFrontWidthDoubleAnimation.From = rightChart.Width;
            rightToFrontWidthDoubleAnimation.To = frontChart.Width;
            storyBoard.Children.Add(rightToFrontWidthDoubleAnimation);
            Storyboard.SetTarget(rightToFrontWidthDoubleAnimation, rightChart);
            Storyboard.SetTargetProperty(rightToFrontWidthDoubleAnimation, new PropertyPath(Ellipse.HeightProperty));

            DoubleAnimation rightToFrontHeightDoubleAnimation = new DoubleAnimation();
            rightToFrontHeightDoubleAnimation.Duration = duration;
            rightToFrontHeightDoubleAnimation.From = rightChart.Height;
            rightToFrontHeightDoubleAnimation.To = frontChart.Height;
            storyBoard.Children.Add(rightToFrontHeightDoubleAnimation);
            Storyboard.SetTarget(rightToFrontHeightDoubleAnimation, rightChart);
            Storyboard.SetTargetProperty(rightToFrontHeightDoubleAnimation, new PropertyPath(Ellipse.WidthProperty));


            // front to left  
            storyBoard.Children.Add(ThicknessAnimation.Create(frontChart, Ellipse.MarginProperty, duration, frontChart.Margin, leftChart.Margin));

            DoubleAnimation frontToLeftWidthDoubleAnimation = new DoubleAnimation();
            frontToLeftWidthDoubleAnimation.Duration = duration;
            frontToLeftWidthDoubleAnimation.From = frontChart.Width;
            frontToLeftWidthDoubleAnimation.To = leftChart.Width;
            storyBoard.Children.Add(frontToLeftWidthDoubleAnimation);
            Storyboard.SetTarget(frontToLeftWidthDoubleAnimation, frontChart);
            Storyboard.SetTargetProperty(frontToLeftWidthDoubleAnimation, new PropertyPath(Ellipse.HeightProperty));

            DoubleAnimation frontToLeftHeightDoubleAnimation = new DoubleAnimation();
            frontToLeftHeightDoubleAnimation.Duration = duration;
            frontToLeftHeightDoubleAnimation.From = frontChart.Height;
            frontToLeftHeightDoubleAnimation.To = leftChart.Height;
            storyBoard.Children.Add(frontToLeftHeightDoubleAnimation);
            Storyboard.SetTarget(frontToLeftHeightDoubleAnimation, frontChart);
            Storyboard.SetTargetProperty(frontToLeftHeightDoubleAnimation, new PropertyPath(Ellipse.WidthProperty));


            // left to right  
            storyBoard.Children.Add(ThicknessAnimation.Create(leftChart, Ellipse.MarginProperty, duration, leftChart.Margin, rightChart.Margin));


            DoubleAnimation leftToRightWidthDoubleAnimation = new DoubleAnimation();
            leftToRightWidthDoubleAnimation.Duration = duration;
            leftToRightWidthDoubleAnimation.From = leftChart.Width;
            leftToRightWidthDoubleAnimation.To = rightChart.Width;
            storyBoard.Children.Add(leftToRightWidthDoubleAnimation);
            Storyboard.SetTarget(leftToRightWidthDoubleAnimation, leftChart);
            Storyboard.SetTargetProperty(leftToRightWidthDoubleAnimation, new PropertyPath(Ellipse.HeightProperty));

            DoubleAnimation leftToRightHeightDoubleAnimation = new DoubleAnimation();
            leftToRightHeightDoubleAnimation.Duration = duration;
            leftToRightHeightDoubleAnimation.From = leftChart.Height;
            leftToRightHeightDoubleAnimation.To = rightChart.Height;
            storyBoard.Children.Add(leftToRightHeightDoubleAnimation);
            Storyboard.SetTarget(leftToRightHeightDoubleAnimation, leftChart);
            Storyboard.SetTargetProperty(leftToRightHeightDoubleAnimation, new PropertyPath(Ellipse.WidthProperty));

            storyBoard.Begin();

        }

        void storyBoard_Completed(object sender, EventArgs e)
        { 
            var ellipses = new List<Ellipse>();
            ellipses.AddRange(ChartList.ToArray());
            ChartList.Clear();
            ChartList.Add(ellipses[1]);
            ChartList.Add(ellipses[2]);
            ChartList.Add(ellipses[0]);
            if (recur)
            {
                recur = false; 
                Animate();
            }
            else 
            {
                AddChartsEvents();
            }
        }
         

        Point GetEllipseCenter(Ellipse ellipse)
        {
            return new Point(ellipse.Margin.Left + ellipse.Width / 2, ellipse.Margin.Top + ellipse.Height / 2);
        }

        protected void RepositionChart(Size size)
        {
            for (int i = 0; i < ChartList.Count; i++)
            {
                var width = ChartList[i].Width;
                var height = ChartList[i].Height;
                var margin = new Thickness()
                {
                    Left = (MarginList[i].Left ) * size.Width / OriginWidth - width / 2,
                    Top = (MarginList[i].Top ) * size.Height / OriginHeight - height / 2
                };
                ChartList[i].SetValue(Ellipse.MarginProperty, margin);

                var newWidth = width* size.Width / OriginWidth;
                var newHeight = height* size.Height / OriginHeight ;
                ChartList[i].SetValue(Ellipse.WidthProperty, newWidth);
                ChartList[i].SetValue(Ellipse.HeightProperty, newHeight);
            }
        }

        protected void AddChartsEvents()
        {
            leftChart.MouseLeftButtonUp += new MouseButtonEventHandler(leftChart_MouseLeftButtonUp);
            frontChart.MouseLeftButtonUp += new MouseButtonEventHandler(frontChart_MouseLeftButtonUp);
            rightChart.MouseLeftButtonUp += new MouseButtonEventHandler(rightChart_MouseLeftButtonUp);
            //this.SizeChanged += new SizeChangedEventHandler(MainWindow_SizeChanged); 
        }

        protected void RemoveChartsEvents()
        {
            leftChart.MouseLeftButtonUp -= new MouseButtonEventHandler(leftChart_MouseLeftButtonUp);
            frontChart.MouseLeftButtonUp -= new MouseButtonEventHandler(frontChart_MouseLeftButtonUp);
            rightChart.MouseLeftButtonUp -= new MouseButtonEventHandler(rightChart_MouseLeftButtonUp);
            //this.SizeChanged -= new SizeChangedEventHandler(MainWindow_SizeChanged); 
        }

        #endregion

        #region Load

        private void MainPage_Loaded(object sender, RoutedEventArgs e)
        {
            ChartList = new List<Ellipse>() { 
                leftChart,frontChart,rightChart
            };
            MarginList = new List<Thickness>() { 
                leftChart.Margin,frontChart.Margin,rightChart.Margin
            };
            //OriginHeight = this.Height;
            //OriginWidth = this.Width;
            //RepositionChart(new Size() { Height = OriginHeight, Width = OriginWidth });
            AddChartsEvents();
        }

        #endregion

        private void MainWindow_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            //RepositionChart(e.NewSize);
        }

        private int FindIndexByName(string ellipseName)
        {
            var ellipse = ChartList.SingleOrDefault(c => c.Name == ellipseName);
            if (ellipse == null)
                return -1;
            return ChartList.IndexOf(ellipse);
        }
    }

    public class ThicknessWrapper : FrameworkElement
    {

        public FrameworkElement Target
        {
            get
            {
                return (FrameworkElement)GetValue(TargetProperty);
            }
            set
            {
                SetValue(TargetProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for Target.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty TargetProperty =
            DependencyProperty.Register("Target", typeof(FrameworkElement), typeof(ThicknessWrapper), new PropertyMetadata(null, OnTargetChanged));


        static void OnTargetChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            ThicknessWrapper sender = (ThicknessWrapper)source;
            //sender.UpdateMargin();
        }

        public DependencyProperty DependencyProperty
        {
            get;
            set;
        }


        public String PropertyName
        {
            get
            {
                return (String)GetValue(PropertyNameProperty);
            }
            set
            {
                SetValue(PropertyNameProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for PropertyName.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PropertyNameProperty =
            DependencyProperty.Register("PropertyName", typeof(String), typeof(ThicknessWrapper), new PropertyMetadata("Margin"));



        public Side Side
        {
            get
            {
                return (Side)GetValue(SideProperty);
            }
            set
            {
                SetValue(SideProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for Side.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SideProperty =
            DependencyProperty.Register("Side", typeof(Side), typeof(ThicknessWrapper), new PropertyMetadata(Side.Left, OnSideChanged));


        static void OnSideChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            ThicknessWrapper sender = (ThicknessWrapper)source;
            //sender.UpdateMargin();
        }


        public Point Value
        {
            get
            {
                return (Point)GetValue(ValueProperty);
            }
            set
            {
                SetValue(ValueProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(Point), typeof(ThicknessWrapper), new PropertyMetadata(new Point(0.0,0.0), OnValueChanged));


        static void OnValueChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            ThicknessWrapper sender = (ThicknessWrapper)source;
            sender.UpdateMargin();
        }





        static void OnPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs args)
        {
            ThicknessWrapper sender = (ThicknessWrapper)source;
            //sender.UpdateMargin();
        }

        private void UpdateMargin()
        {
            if (Target != null)
            {
                var thicknessProperty = Target.GetValue(FrameworkElement.MarginProperty);
                var currentThickness = (Thickness)Target.GetValue(FrameworkElement.MarginProperty);
                var nextThickness = new Thickness(
                    CalculateThickness(Side.Left, currentThickness.Left),
                    CalculateThickness(Side.Top, currentThickness.Top),
                    currentThickness.Right,
                    currentThickness.Bottom
                    );

                Target.SetValue(FrameworkElement.MarginProperty, nextThickness);
            }
        }

        private double CalculateThickness(Side sideToCalculate, double currentValue)
        {
            return sideToCalculate == Side.Left ? Value.X : Value.Y;
        }


    }

    [Flags]
    public enum Side
    {
        Left = 1,
        Top = 2,
        Right = 4,
        Bottom = 8,
        All = 15
    }

    public class ThicknessAnimationX
    {
        public static readonly DependencyProperty ElementProperty = DependencyProperty.RegisterAttached("Element", typeof(DependencyObject), typeof(DoubleAnimation), new PropertyMetadata(new PropertyChangedCallback(OnElementPropertyChanged)));

        // The time along the animation from 0-1
        public static DependencyProperty TimeProperty = DependencyProperty.RegisterAttached("Time", typeof(double), typeof(DoubleAnimation), new PropertyMetadata(OnTimeChanged));

        // The object being animated
        public static DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(DependencyObject), typeof(ThicknessAnimationX), null);
        public static DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached("TargetProperty", typeof(DependencyProperty), typeof(DependencyObject), null);

        public static readonly DependencyProperty FromProperty = DependencyProperty.RegisterAttached("From", typeof(Thickness), typeof(DoubleAnimation), null);
        public static readonly DependencyProperty ToProperty = DependencyProperty.RegisterAttached("To", typeof(Thickness), typeof(DoubleAnimation), null);

        public static void SetElement(DependencyObject o, DependencyObject value)
        {
            o.SetValue(ElementProperty, value);
        }

        public static DependencyObject GetElement(DependencyObject o)
        {
            return (DependencyObject)o.GetValue(ElementProperty);
        }

        private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.NewValue != null)
            {
                DoubleAnimation doubleAnimation = (DoubleAnimation)d;

                doubleAnimation.SetValue(TargetProperty, e.NewValue);
                doubleAnimation.From = 0;
                doubleAnimation.To = 1;
                doubleAnimation.SetValue(TargetPropertyProperty, FrameworkElement.MarginProperty);
                Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(ThicknessAnimationX.Time)"));
                Storyboard.SetTarget(doubleAnimation, doubleAnimation);
            }
        }


        private static void OnTimeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            DoubleAnimation animation = (DoubleAnimation)sender;
            double time = GetTime(animation);
            Thickness from = (Thickness)sender.GetValue(FromProperty);
            Thickness to = (Thickness)sender.GetValue(ToProperty);
            DependencyProperty targetProperty = (DependencyProperty)sender.GetValue(TargetPropertyProperty);
            DependencyObject target = (DependencyObject)sender.GetValue(TargetProperty);
            target.SetValue(targetProperty, new Thickness((to.Left - from.Left) * time + from.Left,
                                                          (to.Top - from.Top) * time + from.Top,
                                                          (to.Right - from.Right) * time + from.Right,
                                                          (to.Bottom - from.Bottom) * time + from.Bottom));
        }

        public static double GetTime(DoubleAnimation animation)
        {
            return (double)animation.GetValue(TimeProperty);
        }

        public static void SetTime(DoubleAnimation animation, double value)
        {
            animation.SetValue(TimeProperty, value);
        }

        public static Thickness GetFrom(DoubleAnimation animation)
        {
            return (Thickness)animation.GetValue(FromProperty);
        }

        public static void SetFrom(DoubleAnimation animation, Thickness value)
        {
            animation.SetValue(FromProperty, value);
        }

        public static Thickness GetTo(DoubleAnimation animation)
        {
            return (Thickness)animation.GetValue(ToProperty);
        }

        public static void SetTo(DoubleAnimation animation, Thickness value)
        {
            animation.SetValue(ToProperty, value);
        }
    }   

    public class ThicknessAnimation  
    {  
        // The time along the animation from 0-1 
        public static DependencyProperty TimeProperty = DependencyProperty.RegisterAttached("Time", typeof(double), typeof(DoubleAnimation), new PropertyMetadata(OnTimeChanged)); 
        // The object being animated  
        public static DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(DependencyObject), typeof(ThicknessAnimation), null);  
        // The thickness we're animating to  
        public static DependencyProperty FromProperty = DependencyProperty.RegisterAttached("From", typeof(Thickness), typeof(DependencyObject), null);  
        // The tickness we're animating from 
        public static DependencyProperty ToProperty = DependencyProperty.RegisterAttached("To", typeof(Thickness), typeof(DependencyObject), null);  
        // The target property to animate to.  Should have a property type of Thickness 
        public static DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached("TargetProperty", typeof(DependencyProperty), typeof(DependencyObject), null);  
        /// <summary>  
        /// Creates a Timeline used to animate the thickness of an object  
        /// </summary>  
        /// <param name="target">The object to animate</param> 
        /// <param name="targetProperty">The property on the object to animate</param>  
        /// <param name="duration">The length of the animation</param>  
        /// <param name="from">The begining thickness</param>  
        /// <param name="to">The final thickness</param>  
        /// <returns>A timeline object that can be added to a storyboard</returns>  
        public static Timeline Create(DependencyObject target, DependencyProperty targetProperty, Duration duration, Thickness from, Thickness to)        { 
            DoubleAnimation timeAnimation = new DoubleAnimation() {    From = 0, To = 1, Duration = duration }; 
            timeAnimation.SetValue(TargetProperty, target);  
            timeAnimation.SetValue(TargetPropertyProperty, targetProperty); 
            timeAnimation.SetValue(FromProperty, from); 
            timeAnimation.SetValue(ToProperty, to);  
            Storyboard.SetTargetProperty(timeAnimation, new PropertyPath("(ThicknessAnimation.Time)")); 
            Storyboard.SetTarget(timeAnimation, timeAnimation); 
            return timeAnimation; 
        } 
        /// <summary>  
        /// Silverlight's animation system is animating time from 0 to 1.  When time changes we update the thickness to be time  
        /// percent between from and to  
        /// </summary>  
        private static void OnTimeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)  
        {  
            DoubleAnimation animation = (DoubleAnimation)sender; 
            double time = GetTime(animation); 
            Thickness from = (Thickness)sender.GetValue(FromProperty);
            Thickness to = (Thickness)sender.GetValue(ToProperty); 
            DependencyProperty targetProperty = (DependencyProperty)sender.GetValue(TargetPropertyProperty);
            DependencyObject target = (DependencyObject)sender.GetValue(TargetProperty);  
            target.SetValue(targetProperty, new Thickness((to.Left - from.Left) * time + from.Left,  
                (to.Top - from.Top) * time + from.Top,  
                (to.Right - from.Right) * time + from.Right, 
                (to.Bottom - from.Bottom) * time + from.Bottom)); 
        } 
        public static double GetTime(DoubleAnimation animation)  
        {  
            return (double)animation.GetValue(TimeProperty); 
        } 
        public static void SetTime(DoubleAnimation animation, double value) 
        { 
            animation.SetValue(TimeProperty, value); 
        } 
    }
    public static class ThicknessExtension
    {
        public static Thickness Offset(this Thickness thickness, double leftOffset, double topOffset)
        {
            return new Thickness()
            {
                Left = thickness.Left + leftOffset,
                Top = thickness.Top + topOffset
            };
        }
    }
}
